#!/usr/bin/python
# -*- coding: iso-8859-15 -*-

# Description: Example of use of libIris
# Copyright (C) 2007 by Rafael Treviño Menéndez
# Author: Rafael Treviño Menéndez <skasi.7@gmail.com>
#         Juan Gonzalez <juan@iearobotics.com>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Library General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import sys
import os
import pygtk
import gtk
import gtk.glade
import gnomevfs

sys.path = ['..'] + sys.path

#---------------------------
#-- Modulos de LibIris
#---------------------------
import libIris.Pic16_Bootloader
import libIris.Pic16_Firmware
import libIris.IntelHex


#-- Timeout para la deteccion del bootloader (en segundos)
TIMEOUT = 5

#-- Nombre del fichero .glade con el interfaz de la aplicacion
GLADE_FILE = "pydownloader-gtk.glade"

#-- Directorio donde  buscar el fichero .glade. Si no se encuentra
#-- se usa el directorio actual
GLADE_DIR  = os.path.join(sys.prefix,"share/pydownloader-gtk/glade")

#-------------------------------------------------------------------
#--                  CLASE PRINCIPAL
#-------------------------------------------------------------------
class GpicDownloader:
  """Programa pydownloader-gtk"""


  #-------------------------
  #- INICIALIZACION
  #-------------------------
  def __init__(self):
    
    #-- Localizar fichero .glade 
    #-- primero buscar en el directorio GLADE_DIR
    glade1 = os.path.join(GLADE_DIR,GLADE_FILE);
    
    if os.path.exists(glade1):
      #-- Fichero localizado en GLADE_DIR
      self.fichero_glade = glade1
    else:
      #-- Se probara en el directorio actual
      self.fichero_glade = GLADE_FILE    
    
    #-- Para depurar...
    print "Usando %s" % self.fichero_glade
    
    #-- Abrir fichero .glade 
    try:
      self.wTree = gtk.glade.XML(self.fichero_glade,"window1") 
    except:
      print "Error al abrir fichero .glade"
      sys.exit(-1)      
   

    #-- Obtener la ventana principal y conectar el evento "destroy"
    self.window = self.wTree.get_widget("window1")
    self.window.connect("destroy", gtk.main_quit)
    
    #-- Obtener la barra de estado e inicializarla
    self.statusbar = self.wTree.get_widget("statusbar1")
    self.context_id = self.statusbar.get_context_id("Statusbar example")   
    self.statusbar.push(self.context_id, "Comienzo") 
      
    #-- Obtener el entry con el nombre del fichero. Desconectar cualquier
    #-- senal de Drag and drop    
    self.entry_file = self.wTree.get_widget('entry1')  
    self.entry_file.drag_dest_unset()  
    
    #-- Obtener el combobox con los puertos serie. Desconectar cualquier
    #-- senal de Drag and drop
    self.comboboxentry_serial=self.wTree.get_widget('comboboxentry1')
    self.comboboxentry_serial.child.drag_dest_unset()
      
    #-- Obtener el boton de descarga
    self.button = self.wTree.get_widget("button2")   

    #-- Boton de cancelar
    self.button_cancel = self.wTree.get_widget("button5")
    
    #-- Boton de monitor
    self.button_test = self.wTree.get_widget("button6")
    self.button_monitor = self.wTree.get_widget("button7")
    self.button_servos8 = self.wTree.get_widget("button8")
    self.button_picp    = self.wTree.get_widget("button9")
    self.button_eco     = self.wTree.get_widget("button10")
    
    #-- Obtener referencia a la barra de progreso
    self.progressbar = self.wTree.get_widget('progressbar1')
    
    #-- Conectar todas las senales de retrollamada definidas en el 
    #-- fichero .glade
    dic = { "on_button1_clicked"   : self.download_clicked,
            "on_window1_destroy"   : gtk.main_quit, 
            "on_button3_clicked"   : self.dialog_clicked,
            "on_button5_clicked"   : self.cancelar,
            "on_button6_clicked"   : self.test_button,
            "on_button7_clicked"   : self.firmware_monitor,
            "on_button8_clicked"   : self.firmware_servos8,
            "on_button9_clicked"   : self.firmware_picp,
            "on_button10_clicked"  : self.firmware_eco,
            "on_button11_clicked"  : self.firmware_test2,
          }
    self.wTree.signal_autoconnect(dic)
    
    #-- Activar las senales de "Drag and Drop" para la ventana principal
    #-- Se pueden arrastrar iconos de ficheros en formato .hex
    self.window.drag_dest_set(gtk.DEST_DEFAULT_DROP,[("text/uri-list",0,0)], 0)
    self.window.connect('drag_motion', self.motion_cb)
    self.window.connect('drag_data_received', self.receiveCallback)
    
    #-- No se ha apretado el boton de Cancelar la descarga
    self.cancelar = False
    
    #-- Interfaz Iris a utilizar
    self.iris = None
    
  #---------------------------
  #-- Actualizar el interfaz 
  #---------------------------  
  def update(self):
    while gtk.events_pending():
      gtk.main_iteration_do()

  #------------------------------------------------------------
  #-- Activar todos los botones relacionados con la descarga
  #-- El boton de cancel se desactiva
  #------------------------------------------------------------
  def botones_modo_descarga(self):
    self.button.set_sensitive(True)
    self.button_cancel.set_sensitive(False)
    self.button_monitor.set_sensitive(True)
    self.button_servos8.set_sensitive(True)
    self.button_picp.set_sensitive(True)
    self.button_test.set_sensitive(True)
    self.button_eco.set_sensitive(True)
      
  #-----------------------------------------------------------------------
  #-- Desactivar todos los botones de descarga. El de cancelar se activa  
  #-----------------------------------------------------------------------
  def botones_modo_cancelar(self):
    #-- Hacer sensible boton de cancelar
    self.button_cancel.set_sensitive(True)
    
    #-- Resto de botones no sensibles
    self.button.set_sensitive(False)
    self.button_monitor.set_sensitive(False)
    self.button_servos8.set_sensitive(False)
    self.button_picp.set_sensitive(False)
    self.button_test.set_sensitive(False)
    self.button_eco.set_sensitive(False)  
      
  #------------------------------------------------------
  #-- Funcion necesaria para realizar el Drag and Drop  
  #------------------------------------------------------  
  def motion_cb(self,wid, context, x, y, time):
    return True
  
  #--------------------------------------------------------------------
  #-- Funcion de Drop. Se invoca cuando se ha arrastrado un fichero   
  #-- Y se han recibido los datos
  #--------------------------------------------------------------------
  def receiveCallback(self, widget, context, x, y, selection, targetType,time):
    self.statusbar.push(self.context_id, "Iniciando descarga!") 
    self.update()
    
    #-- Obtener la lista con las URIs arrastradas
    uri1 = selection.data.split('\r\n')
    
    #-- Nos quedamos solo con el primero de los ficheros pasados
    uri = gnomevfs.URI(uri1[0])
    
    #-- Solo se hace la descarga si el fichero es local
    if (uri.is_local == 1):
     
      #-- Leer el fichero con su ruta y meterlo en el "entry"
      self.entry_file.set_text(uri.path)
      
      #-- Activar el boton de descarga
      self.button.clicked()
    else:
      print "Error"   
      self.statusbar.push(self.context_id, "Error!")      

  #-------------------------------------
  #-- Boton de buscar fichero apretado  
  #-------------------------------------
  def dialog_clicked(self,widget):
  
    #-- Crear la ventana con el selector de ficheros
    wTree = gtk.glade.XML(self.fichero_glade,"window2") 
    
    #-- Obtener la ventana y el selector
    self.window2 = wTree.get_widget("window2")
    self.filechooser = wTree.get_widget("filechooserwidget1")
    
    filter = gtk.FileFilter()
    filter.add_pattern("*.hex")
    filter.add_pattern("*.HEX")
    self.filechooser.set_filter(filter)
    
    
    #-- Conectar el boton de abrir
    abrir = wTree.get_widget("button4")
    abrir.connect("clicked",self.abrir)
    
  #-------------------------------------------------------------------
  #-- Boton de abrir fichero apretado (del selector de ficheros).
  #  Se lee el fichero y se mete en el entry
  #-------------------------------------------------------------------  
  def abrir(self,widget):
  
    #-- Leer fichero
    filename = self.filechooser.get_filename()
    
    #-- Guardarlo en el entry
    self.entry_file.set_text(filename)
    
    #-- Matar la ventana de dialogo
    self.window2.destroy()  
    
  #-----------------------------------------------------
  #- Funcion de retrollamada del boton de Cancelar
  #-----------------------------------------------------  
  def cancelar(self,wid):
    self.cancelar = True    
  
  
  #---------------------------------------------------------------------
  #-- Funciones de retrollamda de los botons de descarga de Firmware   
  #---------------------------------------------------------------------
 
  def test_button(self,widget):
    self.download_program(libIris.Pic16_Firmware.ledp1)
    
  def firmware_monitor(self,widget):
    self.download_program(libIris.Pic16_Firmware.generic)
    
  def firmware_servos8(self,widget):
    self.download_program(libIris.Pic16_Firmware.servos8)
    
  def firmware_picp(self,widget):
    self.download_program(libIris.Pic16_Firmware.picp) 

  def firmware_eco(self,widget):
    self.download_program(libIris.Pic16_Firmware.echo)
    
  def firmware_test2(self,widget):
    self.download_program(libIris.Pic16_Firmware.ledp2)  
    

  #--------------------------------------------------------------
  #- Metodo principal para descargar un programa en la skypic
  #--------------------------------------------------------------
  def download_program(self,prog):
    #-- Poner la barra de progreso a 0
    self.progressbar.set_fraction(0.0) 
    self.update()
  
    #-- Desactivar flag de cancelacion
    self.cancelar=False
    
    #------------------------------------
    #-- Abrir puerto serie
    #------------------------------------
    #-- Primero obtener el nombre del dispositivo serie
    entry = self.comboboxentry_serial.child
    serialName = entry.get_text()
    
    try:
      self.iris = libIris.Pic16_Bootloader.Iris(serialName,logCallback=None)
    except libIris.Pic16_Bootloader.IrisError,msg:
    
      #-- Si hay error indicarlo en la barra de estado y abortar
      self.statusbar.push(self.context_id, "%s" % msg) 
      return
      
    #-- Hay que esperar a que detecte el Bootloader
    self.statusbar.push(self.context_id, "Pulse Reset en la skypic") 
    
    #-- Actualizar el interfaz
    self.botones_modo_cancelar()
    self.update()
    
    try:
      self.iris.download(prog,stateCallback=self.state)
    except libIris.Pic16_Bootloader.IrisError,msg:
      self.statusbar.push(self.context_id, "%s" % msg) 
      self.botones_modo_descarga()
      self.update()
      return
  
    self.statusbar.push(self.context_id, "OK")
    self.botones_modo_descarga()

    
  #--------------------------------------------------------------------------
  #-- Boton de descarga. Se inicia el protocolo de descarga del fichero 
  #-- .hex en la skypic
  #-- El hombre del fichero se toma del entry_file.
  #-- El puerto serie se toma del comboboxentry1
  #--------------------------------------------------------------------------  
  def download_clicked(self, widget):
    self.download()
  

  #-----------------------------------------------------------------------
  #-- Funcion de descarga de un fichero .hex. Se toma del entry, se 
  #-- parsea el fichero y finalmente se descarga
  #-----------------------------------------------------------------------
  def download(self):
    
    #----------------------------------------
    #-- Abrir y parsear el fichero .hex
    #----------------------------------------
    #-- Obtener el nombre
    file = self.entry_file.get_text()
    
    #-- Realizar el parseo
    try:
      hr = libIris.IntelHex.HexReader (file)
    except libIris.IntelHex.ReaderError,msg:
      self.statusbar.push(self.context_id, "%s" % msg) 
      return      
    
    #----------------------------------
    #-- Realizar la descarga!!
    #----------------------------------
    
    #-- Obtener el programa en el formato correcto
    program = hr.dataBlocks16()
    
    self.download_program(program)
  
  #------------------------------------------------------------------------
  #-- Funcion de retrollamada de libIris. Segun el estado de la descarga
  #-- Se hace una cosa u otra
  #------------------------------------------------------------------------  
  def state(self,op,inc,total):
    
    #-----------------------------
    #-- Comienzo de descarga
    #-----------------------------
    if op==libIris.Pic16_Bootloader.WRITING_START:

      #-- Barra de progreso a cero
      self.progressbar.set_fraction(0.0)

      #-- Actualizar barra de status
      self.statusbar.push(self.context_id, "Descargando....")
      self.update()      
      return True
      
    
    #------------------------------
    #-- Incremento en la descarga  
    #------------------------------    
    elif op==libIris.Pic16_Bootloader.WRITING_INC:  
      self.progressbar.set_fraction(float(inc)/float(total)) 
      self.update()
      
      #-- Comprobar si se ha apretado boton de Cancelar
      if self.cancelar:
        return False
        
      return True
    
    #-------------------------------
    #-- Fin de la descarga
    #-------------------------------
    elif op==libIris.Pic16_Bootloader.WRITING_END: 
      self.progressbar.set_fraction(1.0) 
      return True
   
    #---------------------------------------------------
    #-- Comienzo de la identificacion del bootloader    
    #---------------------------------------------------    
    elif op==libIris.Pic16_Bootloader.IDENT_START:
      return True
      
      
    #-----------------------------------------------------------------
    #-- Respuesta no recibida del bootloader tras un mini-timeout    
    #-----------------------------------------------------------------
    elif op==libIris.Pic16_Bootloader.IDENT_NACK:
    
      #-- Mientras que el tiempo total acumulado sea menor que el 
      #-- TIMEOUT indicado, continuar esperando
      self.update()
      
      #-- Si apretado boton de cancelar abortar...
      if self.cancelar:
        return False
      
      if total<=TIMEOUT:
       return True
      else :
        return False  

    

#------------------------
#-- Programa principal  
#------------------------
if __name__ == "__main__":
  GpicDownloader()
  gtk.main()
